package web.suzy.oj.dao.problem.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileReader;
import cn.hutool.core.io.file.FileWriter;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
import web.suzy.oj.dao.judge.JudgeEntityService;
import web.suzy.oj.dao.problem.*;
import web.suzy.oj.exception.ProblemIDRepeatException;
import web.suzy.oj.mapper.ProblemMapper;
import web.suzy.oj.pojo.bo.Pair_;
import web.suzy.oj.pojo.dto.ProblemDTO;
import web.suzy.oj.pojo.entity.problem.*;
import web.suzy.oj.pojo.vo.ImportProblemVO;
import web.suzy.oj.pojo.vo.ProblemCountVO;
import web.suzy.oj.pojo.vo.ProblemVO;
import web.suzy.oj.utils.Constants;

import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * YangSuzy 软件工作室
 * 类名: ProblemEntityServiceImpl
 * 描述: ---- 待定 -----
 * 功能: ---- 待定 -----
 * =============未完成===============
 *
 * @author YangSuzy
 * Date: 2022/11/20 16:08
 */
@Service
public class ProblemEntityServiceImpl extends ServiceImpl<ProblemMapper, Problem> implements ProblemEntityService {

    @Autowired
    private ProblemMapper problemMapper;

    @Autowired
    private JudgeEntityService judgeEntityService;

    @Autowired
    private ProblemCaseEntityService problemCaseEntityService;

    @Autowired
    private ProblemLanguageEntityService problemLanguageEntityService;

    @Autowired
    private TagEntityService tagEntityService;

    @Autowired
    private ProblemTagEntityService problemTagEntityService;

    @Autowired
    private ApplicationContext applicationContext;

    @Autowired
    private CodeTemplateEntityService codeTemplateEntityService;

    /**
     * 处理 EOL字符 结尾的空格和换行符
     */
    private final static Pattern EOL_PATTERN = Pattern.compile("[^\\S\\n]+(?=\\n)");

    /**
     * 方法名: getProblemList
     * 描述: 获取题目列表
     *
     * @param limit       每页显示数量
     * @param currentPage 当前页
     * @param pid         题目ID
     * @param title       题目标题
     * @param difficulty  题目难度
     * @param tid         标签ID
     * @return com.baomidou.mybatisplus.extension.plugins.pagination.Page<web.suzy.oj.pojo.vo.ProblemVO>
     * @date 2022/11/20 16:08
     * @auther YangSuzy
     **/
    @Override
    public Page<ProblemVO> getProblemList(int limit, int currentPage, Long pid, String title, Integer difficulty, List<Long> tid) {
        //新建分页对象
        Page<ProblemVO> page = new Page<>(currentPage, limit);
        //获取标签个数
        Integer tagListSize = null;
        if (tid != null) {
            tid = tid.stream().distinct().collect(Collectors.toList());
            tagListSize = tid.size();
        }
        //根据传入信息查询题目列表
        List<ProblemVO> problemList = problemMapper.getProblemList(
                page, pid, title, difficulty, tid, tagListSize);

        if (problemList.size() > 0) {
            //获取题目ID列表
            List<Long> pidList = problemList.stream().map(ProblemVO::getPid).collect(Collectors.toList());
            //根据题目ID列表查询所有题目的提交统计
            List<ProblemCountVO> problemListCount = judgeEntityService.getProblemListCount(pidList);
            for (ProblemVO problemVo : problemList) {
                for (ProblemCountVO problemCountVo : problemListCount) {
                    //设置对应题目的提交统计
                    if (problemVo.getPid().equals(problemCountVo.getPid())) {
                        problemVo.setProblemCountVo(problemCountVo);
                        break;
                    }
                }
            }
        }
        //返回查询的题目列表
        return page.setRecords(problemList);
    }

    /**
     * 方法名: adminAddProblem
     * 描述: 管理员添加题目
     *
     * @param problemDto 题目信息数据传输对象
     * @return boolean
     * @date 2022/11/21 16:45
     * @auther YangSuzy
     **/
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean adminAddProblem(ProblemDTO problemDto) {
        //从传输对象中获取题目信息
        Problem problem = problemDto.getProblem();
        //默认判题模式时将特判语言和代码设置为空
        if (Constants.JudgeMode.DEFAULT.getMode().equals(problemDto.getJudgeMode())) {
            problem.setSpjLanguage(null)
                    .setSpjCode(null);
        }
        //设置测试样例的版本号
        problem.setCaseVersion(String.valueOf(System.currentTimeMillis()));

        //如果没有提供题目ID,则生成四位UUID作为题目ID的数字
        if (problem.getProblemId() == null) {
            problem.setProblemId(UUID.fastUUID().toString());
            problemMapper.insert(problem);
            //将题目ID统一为 P + 四位数字
            UpdateWrapper<Problem> problemUpdateWrapper = new UpdateWrapper<>();
            problemUpdateWrapper.set("problem_id", "P" + problem.getId());
            problemUpdateWrapper.eq("id", problem.getId());
            problemMapper.update(null, problemUpdateWrapper);
            problem.setProblemId("P" + problem.getId());
        } else {
            //输入的ID全部大写
            String problemId = problem.getProblemId().toUpperCase();
            //problem_id唯一性检查
            QueryWrapper<Problem> problemQueryWrapper = new QueryWrapper<>();
            problemQueryWrapper.eq("problem_id", problemId);
            int existedProblem = problemMapper.selectCount(problemQueryWrapper);
            if (existedProblem > 0) {
                throw new ProblemIDRepeatException("The problem_id [" + problemId + "] already exists. Do not reuse it!");
            }
            problem.setProblemId(problemId);
            problemMapper.insert(problem);
        }
        //检查题目是否创建成功
        Long pid = problem.getId();
        if (pid == null) {
            throw new ProblemIDRepeatException("The problem with problem_id [" + problem.getProblemId() + "] insert failed!");
        }

        //为新的题目添加对应的语言
        List<ProblemLanguage> problemLanguageList = new LinkedList<>();
        for (Language language : problemDto.getLanguages()) {
            problemLanguageList.add(new ProblemLanguage().setPid(pid).setLid(language.getId()));
        }
        boolean addLangToProblemResult = problemLanguageEntityService.saveOrUpdateBatch(problemLanguageList);

        //为新的题目添加对应的代码模板
        boolean addProblemCodeTemplate = true;
        if (problemDto.getCodeTemplates() != null && problemDto.getCodeTemplates().size() > 0) {
            for (CodeTemplate codeTemplate : problemDto.getCodeTemplates()) {
                codeTemplate.setPid(pid);
            }
            addProblemCodeTemplate = codeTemplateEntityService.saveOrUpdateBatch(problemDto.getCodeTemplates());
        }

        //为新的题目添加对应的测试用例
        boolean addCasesToProblemResult = true;
        //如果选择上传测试文件需要遍历对应文件夹读取数据
        if (problemDto.getIsUploadTestCase()) {
            //获取传输对象中的测试用例列表
            List<ProblemCase> problemCases = problemDto.getSamples();
            if (problemCases.size() == 0) {
                throw new RuntimeException("The test cases of problem must not be empty!");
            }
            //OI题目需要统计测试用例总分
            int sumScore = 0;
            for (ProblemCase problemCase : problemCases) {
                if (problemCase.getScore() != null) {
                    sumScore += problemCase.getScore();
                }
                if (StringUtils.isEmpty(problemCase.getOutput())) {
                    //测试用例的输出用例为空时，截取输入用例的文件名设置对应的输出文件
                    String filePreName = problemCase.getInput().split("\\.")[0];
                    problemCase.setOutput(filePreName + ".out");
                }
                problemCase.setPid(pid);
            }
            //根据测试用例的统计设置OI总分数
            if (problem.getType().intValue() == Constants.Type.TYPE_OI.getCode()) {
                UpdateWrapper<Problem> problemUpdateWrapper = new UpdateWrapper<>();
                problemUpdateWrapper.eq("id", pid)
                        .set("io_score", sumScore);
                problemMapper.update(null, problemUpdateWrapper);
            }
            addCasesToProblemResult = problemCaseEntityService.saveOrUpdateBatch(problemCases);

            //获取上传测试用例的文件夹
            String testcaseDir = problemDto.getUploadTestcaseDir();
            //获取代理bean对象执行异步方法 --> 根据测试文件初始info
            applicationContext.getBean(ProblemEntityServiceImpl.class).initUploadTestCase(
                    problemDto.getJudgeMode(),
                    problem.getJudgeCaseMode(),
                    problem.getCaseVersion(),
                    pid,
                    testcaseDir,
                    problemDto.getSamples());
        } else {
            //oi题目需要求取平均值，给每个测试点初始oi的score值，默认总分100分
            if (problem.getType().intValue() == Constants.Type.TYPE_OI.getCode()) {
                for (ProblemCase problemCase : problemDto.getSamples()) {
                    //设置好新题目的pid和累加总分数
                    problemCase.setPid(pid);
                }
                //根据测评用例模式计算oi题目的总分
                int sumScore = calProblemTotalScore(problem.getJudgeCaseMode(), problemDto.getSamples());
                addCasesToProblemResult = problemCaseEntityService.saveOrUpdateBatch(problemDto.getSamples());
                UpdateWrapper<Problem> problemUpdateWrapper = new UpdateWrapper<>();
                problemUpdateWrapper.eq("id", pid)
                        .set("io_score", sumScore);
                problemMapper.update(null, problemUpdateWrapper);
            } else {
                //ACM模式下设置好新题目的pid后直接保存
                problemDto.getSamples().forEach(problemCase -> problemCase.setPid(pid));
                addCasesToProblemResult = problemCaseEntityService.saveOrUpdateBatch(problemDto.getSamples());
            }
            //异步处理手动输入的测试用例
            initHandTestCase(problemDto.getJudgeMode(),
                    problem.getJudgeCaseMode(),
                    problem.getCaseVersion(),
                    pid,
                    problemDto.getSamples());
        }

        //为新的题目添加对应的tag，需要判断tag是原表已有的数据或者新加入的数据
        List<ProblemTag> problemTagList = new LinkedList<>();
        if (problemDto.getTags() != null) {
            for (Tag tag : problemDto.getTags()) {
                //id为空 表示为原tag表中不存在的 插入后可以获取到对应的tagId
                if (tag.getId() == null) {
                    Tag existedTag = tagEntityService.getOne(
                            new QueryWrapper<Tag>().eq("name", tag.getName()),
                            false);
                    if (existedTag == null) {
                        tagEntityService.save(tag);
                    } else {
                        tag = existedTag;
                    }
                }
                problemTagList.add(new ProblemTag().setTid(tag.getId()).setPid(pid));
            }
        }
        boolean addTagsToProblemResult = true;
        if (problemTagList.size() > 0) {
            addTagsToProblemResult = problemTagEntityService.saveOrUpdateBatch(problemTagList);
        }

        //方法执行成功条件：
        //1.测试用例是否添加成功
        //2.编程语言是否添加成功
        //3.题目标签是否添加成功
        //4.代码模板是否添加成功
        return addCasesToProblemResult && addLangToProblemResult
                && addTagsToProblemResult && addProblemCodeTemplate;
    }

    /**
     * 方法名: adminUpdateProblem
     * 描述: 管理员更新题目
     *
     * @param problemDto 题目信息传输对象
     * @return boolean
     * @date 2022/11/22 12:24
     * @auther YangSuzy
     **/
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean adminUpdateProblem(ProblemDTO problemDto) {
        //从传输对象中获取题目信息
        Problem problem = problemDto.getProblem();
        //默认判题模式时将特判语言和代码设置为空
        if (Constants.JudgeMode.DEFAULT.getMode().equals(problemDto.getJudgeMode())) {
            problem.setSpjLanguage(null).setSpjCode(null);
        }

        //problem_id唯一性检查
        String problemId = problem.getProblemId().toUpperCase();
        QueryWrapper<Problem> problemQueryWrapper = new QueryWrapper<>();
        problemQueryWrapper.eq("problem_id", problemId);
        Problem existedProblem = problemMapper.selectOne(problemQueryWrapper);
        problem.setProblemId(problem.getProblemId().toUpperCase());
        //获取题目ID，后面许多表的更新或删除需要用到
        long pid = problemDto.getProblem().getId();
        //校验题目ID
        if (existedProblem != null && existedProblem.getId() != pid) {
            throw new RuntimeException("The problem_id [" + problemId + "] already exists. Do not reuse it!");
        }

        Map<String, Object> map = new HashMap<>();
        map.put("pid", pid);
        //查询出原来题目的关联表数据
        List<ProblemLanguage> oldProblemLanguages = (List<ProblemLanguage>) problemLanguageEntityService.listByMap(map);
        List<ProblemCase> oldProblemCases = (List<ProblemCase>) problemCaseEntityService.listByMap(map);
        List<CodeTemplate> oldProblemTemplate = (List<CodeTemplate>) codeTemplateEntityService.listByMap(map);
        List<ProblemTag> oldProblemTags = (List<ProblemTag>) problemTagEntityService.listByMap(map);

        //题目标签
        Map<Long, Integer> mapOldPT = new HashMap<>();
        //题目语言
        Map<Long, Integer> mapOldPL = new HashMap<>();
        //题目模板代码
        Map<Integer, Integer> mapOldPCT = new HashMap<>();
        //需要删除的测试用例
        List<Long> needDeleteProblemCases = new LinkedList<>();
        //题目测试用例
        HashMap<Long, ProblemCase> oldProblemMap = new HashMap<>();

        //登记一下原有的tag的id
        oldProblemTags.stream().forEach(problemTag -> {
            mapOldPT.put(problemTag.getTid(), 0);
        });
        //登记一下原有的language的id
        oldProblemLanguages.stream().forEach(problemLanguage -> {
            mapOldPL.put(problemLanguage.getLid(), 0);
        });
        //登记一下原有的codeTemplate的id
        oldProblemTemplate.stream().forEach(codeTemplate -> {
            mapOldPCT.put(codeTemplate.getId(), 0);
        });
        //登记一下原有的case的id
        oldProblemCases.stream().forEach(problemCase -> {
            needDeleteProblemCases.add(problemCase.getId());
            oldProblemMap.put(problemCase.getId(), problemCase);
        });

        //处理tag表与problem_tag表的删除与更新
        //存储新的problem_tag表数据
        List<ProblemTag> problemTagList = new LinkedList<>();
        for (Tag tag : problemDto.getTags()) {
            if (tag.getId() == null) {
                //没有主键表示为新添加的标签
                boolean addTagResult = tagEntityService.save(tag);
                if (addTagResult) {
                    problemTagList.add(new ProblemTag()
                            .setPid(pid).setTid(tag.getId()));
                }
            } else if (mapOldPT.getOrDefault(tag.getId(), null) == null) {
                //已存在该tag，新添加至该题目
                problemTagList.add(new ProblemTag()
                        .setPid(pid).setTid(tag.getId()));
            } else {
                //已有主键的需要记录，若在problem_tag原有的标签消失，表明需要删除该标签
                //更新记录，说明该tag未删除
                mapOldPT.put(tag.getId(), 1);
            }
        }
        //放入需要删除的tagId列表
        List<Long> needDeleteTids = new LinkedList<>();
        for (Long key : mapOldPT.keySet()) {
            //记录表中没有更新原来的存在Tid，则表明该tag已不被该problem使用
            if (mapOldPT.get(key) == 0) {
                needDeleteTids.add(key);
            }
        }
        //执行批量删除操作
        boolean deleteTagsFromProblemResult = true;
        if (needDeleteTids.size() > 0) {
            QueryWrapper<ProblemTag> tagWrapper = new QueryWrapper<>();
            tagWrapper.eq("pid", pid).in("tid", needDeleteTids);
            deleteTagsFromProblemResult = problemTagEntityService.remove(tagWrapper);
        }
        //执行批量插入操作
        boolean addTagsToProblemResult = true;
        if (problemTagList.size() > 0) {
            addTagsToProblemResult = problemTagEntityService.saveOrUpdateBatch(problemTagList);
        }

        //处理code_template表
        boolean deleteTemplate = true;
        boolean saveOrUpdateCodeTemplate = true;
        for (CodeTemplate codeTemplate : problemDto.getCodeTemplates()) {
            if (codeTemplate.getId() != null) {
                //标记保留的代码模板
                mapOldPCT.put(codeTemplate.getId(), 1);
            }
        }
        //需要删除的模板
        List<Integer> needDeleteCTs = new LinkedList<>();
        for (Integer key : mapOldPCT.keySet()) {
            if (mapOldPCT.get(key) == 0) {
                needDeleteCTs.add(key);
            }
        }
        //批量删除和更新
        if (needDeleteCTs.size() > 0) {
            deleteTemplate = codeTemplateEntityService.removeByIds(needDeleteCTs);
        }
        if (problemDto.getCodeTemplates().size() > 0) {
            saveOrUpdateCodeTemplate = codeTemplateEntityService.saveOrUpdateBatch(problemDto.getCodeTemplates());
        }


        //处理problem_language表的更新与删除

        //根据上传来的language列表的每一个name字段查询对应的language表的id，更新problem_language
        //构建problem_language实体列表
        List<ProblemLanguage> problemLanguageList = new LinkedList<>();
        for (Language language : problemDto.getLanguages()) {
            if (mapOldPL.get(language.getId()) != null) {
                //标记保留的语言
                mapOldPL.put(language.getId(), 1);
            } else {
                //没有该语言的ID记录，则表明为新添加的language
                problemLanguageList.add(new ProblemLanguage().setLid(language.getId()).setPid(pid));
            }
        }
        //放入需要删除的languageId列表
        List<Long> needDeleteLids = new LinkedList<>();
        for (Long key : mapOldPL.keySet()) {
            if (mapOldPL.get(key) == 0) {
                needDeleteLids.add(key);
            }
        }
        boolean deleteLanguagesFromProblemResult = true;
        if (needDeleteLids.size() > 0) {
            QueryWrapper<ProblemLanguage> LangWrapper = new QueryWrapper<>();
            LangWrapper.eq("pid", pid).in("lid", needDeleteLids);
            // 执行批量删除操作
            deleteLanguagesFromProblemResult = problemLanguageEntityService.remove(LangWrapper);
        }
        // 执行批量添加操作
        boolean addLanguagesToProblemResult = true;
        if (problemLanguageList.size() > 0) {
            addLanguagesToProblemResult = problemLanguageEntityService.saveOrUpdateBatch(problemLanguageList);
        }

        //处理problem_case表的增加与删除
        boolean checkProblemCase = true;
        if (problemDto.getSamples().size() > 0) {
            //新增加的case列表
            List<ProblemCase> newProblemCaseList = new LinkedList<>();
            //需要修改的case列表
            List<ProblemCase> needUpdateProblemCaseList = new LinkedList<>();
            //遍历上传的case列表，如果还存在，则从需要删除的测试样例列表移除该id
            for (ProblemCase problemCase : problemDto.getSamples()) {
                if (problemCase.getId() != null) {
                    //已存在的case
                    needDeleteProblemCases.remove(problemCase.getId());
                    //跟原先的数据做对比，如果变动 则加入需要修改的case列表
                    ProblemCase oldProblemCase = oldProblemMap.get(problemCase.getId());
                    if (!oldProblemCase.getInput().equals(problemCase.getInput()) ||
                            !oldProblemCase.getOutput().equals(problemCase.getOutput())) {
                        needUpdateProblemCaseList.add(problemCase);
                    } else if (problem.getType().intValue() == Constants.Type.TYPE_OI.getCode()) {
                        //分数变动 或者 subtask分组编号变更
                        if (!Objects.equals(oldProblemCase.getScore(), problemCase.getScore())
                                || !Objects.equals(oldProblemCase.getGroupNum(), problemCase.getGroupNum())) {
                            needUpdateProblemCaseList.add(problemCase);
                        }
                    }
                } else {
                    newProblemCaseList.add(problemCase.setPid(pid));
                }
            }
            //根据测评模式设置oi总分数
            if (problem.getType().intValue() == Constants.Type.TYPE_OI.getCode()) {
                int sumScore = calProblemTotalScore(problem.getJudgeCaseMode(), problemDto.getSamples());
                problem.setIoScore(sumScore);
            }
            //执行批量删除操作
            boolean deleteCasesFromProblemResult = true;
            if (needDeleteProblemCases.size() > 0) {
                deleteCasesFromProblemResult = problemCaseEntityService.removeByIds(needDeleteProblemCases);
            }
            //执行批量添加操作
            boolean addCasesToProblemResult = true;
            if (newProblemCaseList.size() > 0) {
                addCasesToProblemResult = problemCaseEntityService.saveBatch(newProblemCaseList);
            }
            //执行批量修改操作
            boolean updateCasesToProblemResult = true;
            if (needUpdateProblemCaseList.size() > 0) {
                updateCasesToProblemResult = problemCaseEntityService.saveOrUpdateBatch(needUpdateProblemCaseList);
            }
            //测试用例修改标识符
            checkProblemCase = addCasesToProblemResult && deleteCasesFromProblemResult && updateCasesToProblemResult;

            //只要有新添加，修改，删除，或者变更judgeCaseMode 都需要更新版本号 同时更新测试数据
            String caseVersion = String.valueOf(System.currentTimeMillis());
            String testcaseDir = problemDto.getUploadTestcaseDir();
            if (needDeleteProblemCases.size() > 0 || newProblemCaseList.size() > 0
                    || needUpdateProblemCaseList.size() > 0 || !StringUtils.isEmpty(testcaseDir)
                    || (problemDto.getChangeJudgeCaseMode() != null && problemDto.getChangeJudgeCaseMode())) {
                problem.setCaseVersion(caseVersion);
                //如果是选择上传测试文件的，则需要遍历对应文件夹，读取数据，写入数据库,先前的题目数据一并清空。
                if (problemDto.getIsUploadTestCase()) {
                    //获取代理bean对象执行异步方法 --> 根据测试文件初始info
                    applicationContext.getBean(ProblemEntityServiceImpl.class)
                            .initUploadTestCase(problemDto.getJudgeMode(),
                                    problem.getJudgeCaseMode(),
                                    caseVersion,
                                    pid,
                                    testcaseDir,
                                    problemDto.getSamples());
                } else {
                    applicationContext.getBean(ProblemEntityServiceImpl.class)
                            .initHandTestCase(problemDto.getJudgeMode(),
                                    problem.getJudgeCaseMode(),
                                    problem.getCaseVersion(),
                                    pid,
                                    problemDto.getSamples());
                }
            }
            // 变化成spj或interactive或者取消 同时更新测试数据
            else if (problemDto.getChangeModeCode() != null && problemDto.getChangeModeCode()) {
                problem.setCaseVersion(caseVersion);
                if (problemDto.getIsUploadTestCase()) {
                    // 获取代理bean对象执行异步方法 --> 根据测试文件初始info
                    applicationContext.getBean(ProblemEntityServiceImpl.class)
                            .initUploadTestCase(problemDto.getJudgeMode(),
                                    problem.getJudgeCaseMode(),
                                    caseVersion,
                                    pid,
                                    null,
                                    problemDto.getSamples());
                } else {
                    applicationContext.getBean(ProblemEntityServiceImpl.class)
                            .initHandTestCase(problemDto.getJudgeMode(),
                                    problem.getJudgeCaseMode(),
                                    problem.getCaseVersion(),
                                    pid,
                                    problemDto.getSamples());
                }
            }
        }

        // 更新problem表
        boolean problemUpdateResult = problemMapper.updateById(problem) == 1;

        //方法执行成功条件：
        //1.题目表是否更新成功
        //2.测试用例是否更新成功
        //3.题目语言是否更新成功
        //4.题目标签是否更新成功
        //5.代码模板是否更新成功
        return problemUpdateResult && checkProblemCase && deleteLanguagesFromProblemResult && deleteTagsFromProblemResult
                && addLanguagesToProblemResult && addTagsToProblemResult && deleteTemplate && saveOrUpdateCodeTemplate;
    }

    /**
     * 方法名: buildExportProblem
     * 描述: 导出题目数据
     *
     * @param pid             题目ID
     * @param problemCaseList 测试用例列表
     * @param languageMap     编程语言集合
     * @param tagMap          标签集合
     * @return web.suzy.oj.pojo.vo.ImportProblemVO
     * @date 2022/12/3 17:14
     * @auther YangSuzy
     **/
    @Override
    @SuppressWarnings("All")
    public ImportProblemVO buildExportProblem(
            Long pid,
            List<HashMap<String, Object>> problemCaseList,
            HashMap<Long, String> languageMap,
            HashMap<Long, String> tagMap) {
        ImportProblemVO importProblemVo = new ImportProblemVO();
        //设置对应题目的数据
        Problem problem = problemMapper.selectById(pid);
        problem.setCaseVersion(null)
                .setId(null)
                .setAuth(1)
                .setIsUploadCase(true)
                .setAuthor(null)
                .setCreateTime(null)
                .setModifiedTime(null);
        //将题目数据保存到导出题目视图对象中
        HashMap<String, Object> problemMap = new HashMap<>();
        BeanUtil.beanToMap(problem, problemMap, false, true);
        importProblemVo.setProblem(problemMap);
        //保存题目对应的代码模板
        QueryWrapper<CodeTemplate> codeTemplateQueryWrapper = new QueryWrapper<>();
        codeTemplateQueryWrapper.eq("pid", pid).eq("status", true);
        List<CodeTemplate> codeTemplates = codeTemplateEntityService.list(codeTemplateQueryWrapper);
        List<HashMap<String, String>> codeTemplateList = new LinkedList<>();
        for (CodeTemplate codeTemplate : codeTemplates) {
            HashMap<String, String> tmp = new HashMap<>();
            tmp.put("language", languageMap.get(codeTemplate.getLid()));
            tmp.put("code", codeTemplate.getCode());
            codeTemplateList.add(tmp);
        }
        //保存题目代码模板、测评模式、测试用例
        importProblemVo.setCodeTemplates(codeTemplateList);
        importProblemVo.setJudgeMode(problem.getJudgeMode());
        importProblemVo.setSamples(problemCaseList);

        //保存用户额外文件
        if (!StringUtils.isEmpty(problem.getUserExtraFile())) {
            HashMap<String, String> userExtraFileMap = (HashMap<String, String>) JSONUtil.toBean(problem.getUserExtraFile(), Map.class);
            importProblemVo.setUserExtraFile(userExtraFileMap);
        }

        //保存判题额外文件
        if (!StringUtils.isEmpty(problem.getJudgeExtraFile())) {
            HashMap<String, String> judgeExtraFileMap = (HashMap<String, String>) JSONUtil.toBean(problem.getJudgeExtraFile(), Map.class);
            importProblemVo.setUserExtraFile(judgeExtraFileMap);
        }

        //保存题目标签
        QueryWrapper<ProblemTag> problemTagQueryWrapper = new QueryWrapper<>();
        problemTagQueryWrapper.eq("pid", pid);
        List<ProblemTag> problemTags = problemTagEntityService.list(problemTagQueryWrapper);
        importProblemVo.setTags(problemTags.stream().map(problemTag -> tagMap.get(problemTag.getTid())).collect(Collectors.toList()));

        //保存题目编程语言
        QueryWrapper<ProblemLanguage> problemLanguageQueryWrapper = new QueryWrapper<>();
        problemLanguageQueryWrapper.eq("pid", pid);
        List<ProblemLanguage> problemLanguages = problemLanguageEntityService.list(problemLanguageQueryWrapper);
        importProblemVo.setLanguages(problemLanguages.stream().map(problemLanguage -> languageMap.get(problemLanguage.getLid())).collect(Collectors.toList()));

        return importProblemVo;
    }

    /**
     * 方法名: initUploadTestCase
     * 描述: 异步方法：初始化上传文件的测试数据为json文件
     *
     * @param judgeMode       测评模式
     * @param judgeCaseMode   测试用例模式
     * @param version         版本
     * @param problemId       题目ID
     * @param tmpTestcaseDir  临时测试用例文件夹
     * @param problemCaseList 测试用例列表
     * @return void
     * @date 2022/11/21 17:26
     * @auther YangSuzy
     **/
    @Async
    public void initUploadTestCase(String judgeMode,
                                   String judgeCaseMode,
                                   String version,
                                   Long problemId,
                                   String tmpTestcaseDir,
                                   List<ProblemCase> problemCaseList) {
        //存储测试用例的文件夹
        String testCasesDir = Constants.File.TESTCASE_BASE_FOLDER.getPath() + File.separator + "problem_" + problemId;
        //将之前的临时文件夹里面的评测文件全部复制到指定文件夹(覆盖)
        if (!StringUtils.isEmpty(tmpTestcaseDir)) {
            FileUtil.clean(testCasesDir);
            File testCasesDirFile = new File(testCasesDir);
            FileUtil.copyFilesFromDir(new File(tmpTestcaseDir), testCasesDirFile, true);
        }

        //测评用例模式为空时设置为默认全部测评
        if (StringUtils.isEmpty(judgeCaseMode)) {
            judgeCaseMode = Constants.JudgeCaseMode.DEFAULT.getMode();
        }
        //将题目信息转换为json数据
        JSONObject result = new JSONObject();
        result.set("mode", judgeMode);
        result.set("judgeCaseMode", judgeCaseMode);
        result.set("version", version);
        result.set("testCasesSize", problemCaseList.size());

        //新建json数组存储测试用例的json数据
        JSONArray testCaseList = new JSONArray(problemCaseList.size());
        //获取测试用例文件夹中的所有文件名
        List<String> listFileNames = FileUtil.listFileNames(testCasesDir);
        for (ProblemCase problemCase : problemCaseList) {
            JSONObject jsonObject = new JSONObject();
            jsonObject.set("caseId", problemCase.getId());
            //测评模式为OI模式时设置子任务分组信息
            if (judgeCaseMode.equals(Constants.JudgeCaseMode.SUBTASK_AVERAGE.getMode())
                    || judgeCaseMode.equals(Constants.JudgeCaseMode.SUBTASK_LOWEST.getMode())) {
                jsonObject.set("groupNum", problemCase.getGroupNum());
            }
            //将测评用例信息转换为json数据
            jsonObject.set("score", problemCase.getScore());
            jsonObject.set("inputName", problemCase.getInput());
            jsonObject.set("outputName", problemCase.getOutput());
            //移除输入输出文件
            listFileNames.remove(problemCase.getInput());
            listFileNames.remove(problemCase.getOutput());

            //读取输入文件
            FileReader inputFile = new FileReader(
                    testCasesDir + File.separator + problemCase.getInput(),
                    CharsetUtil.UTF_8);
            String input = inputFile.readString()
                    //避免window系统的换行问题
                    .replaceAll("\r\n", "\n")
                    //避免mac系统的换行问题
                    .replaceAll("\r", "\n");
            //将处理后的输入文件重新写入
            FileWriter inputFileWriter = new FileWriter(
                    testCasesDir + File.separator + problemCase.getInput(),
                    CharsetUtil.UTF_8);
            inputFileWriter.write(input);

            //读取输出文件
            String output = "";
            String outputFilePath = testCasesDir + File.separator + problemCase.getOutput();
            if (FileUtil.exist(outputFilePath)) {
                //读取输出文件
                FileReader outputFile = new FileReader(outputFilePath, CharsetUtil.UTF_8);
                output = outputFile.readString()
                        //避免window系统的换行问题
                        .replaceAll("\r\n", "\n")
                        //避免mac系统的换行问题
                        .replaceAll("\r", "\n");
                //将处理后的输出文件重新写入
                FileWriter outFileWriter = new FileWriter(
                        testCasesDir + File.separator + problemCase.getOutput(),
                        CharsetUtil.UTF_8);
                outFileWriter.write(output);
            } else {
                //输出文件不存在时写入空文本
                FileWriter fileWriter = new FileWriter(outputFilePath);
                fileWriter.write("");
            }

            //spj和interactive是根据特判程序输出判断结果，所以无需初始化测试数据
            if (Constants.JudgeMode.DEFAULT.getMode().equals(judgeMode)) {
                //原数据MD5
                jsonObject.set("outputMd5", DigestUtils.md5DigestAsHex(output.getBytes(StandardCharsets.UTF_8)));
                //原数据大小
                jsonObject.set("outputSize", output.getBytes(StandardCharsets.UTF_8).length);
                //去掉全部空格的MD5，用来判断pe
                jsonObject.set("allStrippedOutputMd5", DigestUtils.md5DigestAsHex(output.replaceAll("\\s+", "").getBytes(StandardCharsets.UTF_8)));
                //默认去掉文末空格的MD5
                jsonObject.set("EOFStrippedOutputMd5", DigestUtils.md5DigestAsHex(rtrim(output).getBytes(StandardCharsets.UTF_8)));
            }

            //将每个测试用例的json信息存入json数组
            testCaseList.add(jsonObject);
        }
        result.set("testCases", testCaseList);

        //写入记录文件
        FileWriter infoFile = new FileWriter(testCasesDir + "/info", CharsetUtil.UTF_8);
        infoFile.write(JSONUtil.toJsonStr(result));
        //删除临时上传文件夹
        FileUtil.del(tmpTestcaseDir);
        //删除非测试数据的文件
        listFileNames.remove("info");
        if (!CollectionUtils.isEmpty(listFileNames)) {
            for (String filename : listFileNames) {
                FileUtil.del(testCasesDir + File.separator + filename);
            }
        }
    }

    /**
     * 方法名: initHandTestCase
     * 描述: 初始化手动输入上传的测试数据为json文件
     *
     * @param judgeMode       测评模式
     * @param judgeCaseMode   测试用例模式
     * @param version         版本
     * @param problemId       题目ID
     * @param problemCaseList 题目测试用例列表
     * @return void
     * @date 2022/11/21 17:57
     * @auther YangSuzy
     **/
    @Async
    public void initHandTestCase(String judgeMode,
                                 String judgeCaseMode,
                                 String version,
                                 Long problemId,
                                 List<ProblemCase> problemCaseList) {

        JSONObject result = new JSONObject();
        result.set("mode", judgeMode);
        //测试用例模式为空时设置为默认全部测评
        if (StringUtils.isEmpty(judgeCaseMode)) {
            judgeCaseMode = Constants.JudgeCaseMode.DEFAULT.getMode();
        }
        result.set("judgeCaseMode", judgeCaseMode);
        result.set("version", version);
        result.set("testCasesSize", problemCaseList.size());

        JSONArray testCaseList = new JSONArray(problemCaseList.size());
        String testCasesDir = Constants.File.TESTCASE_BASE_FOLDER.getPath() + File.separator + "problem_" + problemId;
        FileUtil.del(testCasesDir);
        //手动处理上传的测试用例
        for (int index = 0; index < problemCaseList.size(); index++) {
            JSONObject jsonObject = new JSONObject();
            //根据上传顺序对输入文件进行编号
            String inputName = (index + 1) + ".in";
            jsonObject.set("caseId", problemCaseList.get(index).getId());
            //测评模式为OI模式时设置子任务分组信息
            if (judgeCaseMode.equals(Constants.JudgeCaseMode.SUBTASK_AVERAGE.getMode())
                    || judgeCaseMode.equals(Constants.JudgeCaseMode.SUBTASK_LOWEST.getMode())) {
                jsonObject.set("groupNum", problemCaseList.get(index).getGroupNum());
            }
            jsonObject.set("score", problemCaseList.get(index).getScore());
            jsonObject.set("inputName", inputName);
            //生成对应文件
            FileWriter infileWriter = new FileWriter(
                    testCasesDir + "/" + inputName, CharsetUtil.UTF_8);
            //将该测试数据的输入写入到文件
            String inputData = problemCaseList
                    .get(index)
                    .getInput()
                    //避免window系统的换行问题
                    .replaceAll("\r\n", "\n")
                    //避免mac系统的换行问题
                    .replaceAll("\r", "\n");
            infileWriter.write(inputData);

            //根据上传顺序对输出文件进行编号
            String outputName = (index + 1) + ".out";
            jsonObject.set("outputName", outputName);
            //生成对应文件
            String outputData = problemCaseList
                    .get(index)
                    .getOutput()
                    //避免window系统的换行问题
                    .replaceAll("\r\n", "\n")
                    //避免mac系统的换行问题
                    .replaceAll("\r", "\n");
            FileWriter outFile = new FileWriter(
                    testCasesDir + "/" + outputName, CharsetUtil.UTF_8);
            outFile.write(outputData);

            //spj和interactive是根据特判程序输出判断结果，所以无需初始化测试数据
            if (Constants.JudgeMode.DEFAULT.getMode().equals(judgeMode)) {
                //原数据MD5
                jsonObject.set("outputMd5", DigestUtils.md5DigestAsHex(outputData.getBytes(StandardCharsets.UTF_8)));
                //原数据大小
                jsonObject.set("outputSize", outputData.getBytes(StandardCharsets.UTF_8).length);
                //去掉全部空格的MD5，用来判断pe
                jsonObject.set("allStrippedOutputMd5", DigestUtils.md5DigestAsHex(outputData.replaceAll("\\s+", "").getBytes(StandardCharsets.UTF_8)));
                //默认去掉文末空格的MD5
                jsonObject.set("EOFStrippedOutputMd5", DigestUtils.md5DigestAsHex(rtrim(outputData).getBytes(StandardCharsets.UTF_8)));
            }

            testCaseList.add(jsonObject);
        }

        result.set("testCases", testCaseList);
        FileWriter infoFile = new FileWriter(testCasesDir + "/info", CharsetUtil.UTF_8);
        // 写入记录文件
        infoFile.write(JSONUtil.toJsonStr(result));
    }

    /**
     * 方法名: rtrim
     * 描述: 去除每行末尾的空白符
     *
     * @param value
     * @return java.lang.String
     * @date 2022/11/21 17:38
     * @auther YangSuzy
     **/
    public static String rtrim(String value) {
        if (value == null) {
            return null;
        }
        return EOL_PATTERN.matcher(StrUtil.trimEnd(value)).replaceAll("");
    }

    /**
     * 方法名: calProblemTotalScore
     * 描述: 根据测评用例模式计算 OI模式题目总分
     *
     * @param judgeCaseMode   测评用例模式
     * @param problemCaseList 测评用例列表
     * @return java.lang.Integer
     * @date 2022/11/21 17:42
     * @auther YangSuzy
     **/
    private Integer calProblemTotalScore(String judgeCaseMode, List<ProblemCase> problemCaseList) {
        int sumScore = 0;
        if (Constants.JudgeCaseMode.SUBTASK_LOWEST.getMode().equals(judgeCaseMode)) {
            //求每个子任务组中的最低分
            HashMap<Integer, Integer> groupNumMapScore = new HashMap<>();
            for (ProblemCase problemCase : problemCaseList) {
                groupNumMapScore.merge(problemCase.getGroupNum(), problemCase.getScore(), Math::min);
            }
            //设置得分总和
            for (Integer minScore : groupNumMapScore.values()) {
                sumScore += minScore;
            }
        } else if (Constants.JudgeCaseMode.SUBTASK_AVERAGE.getMode().equals(judgeCaseMode)) {
            //求每个子任务组中的平均分
            //预处理 切换成Map <Key: groupNum, Value: <count, sum_score> >
            HashMap<Integer, Pair_<Integer, Integer>> groupNumMapScore = new HashMap<>();
            for (ProblemCase problemCase : problemCaseList) {
                //获取每个子任务组中的存储分数的键值对
                Pair_<Integer, Integer> pair = groupNumMapScore.get(problemCase.getGroupNum());
                if (pair == null) {
                    //初始化子任务组
                    groupNumMapScore.put(problemCase.getGroupNum(), new Pair_<>(1, problemCase.getScore()));
                } else {
                    //记录统计次数
                    int count = pair.getKey() + 1;
                    //记录当前统计次数下的总分
                    int score = pair.getValue() + problemCase.getScore();
                    groupNumMapScore.put(problemCase.getGroupNum(), new Pair_<>(count, score));
                }
            }
            //计算每个子任务组的平均分
            for (Pair_<Integer, Integer> pair : groupNumMapScore.values()) {
                sumScore += (int) Math.round(pair.getValue() * 1.0 / pair.getKey());
            }
        } else {
            //其他模式下记录总分即可
            for (ProblemCase problemCase : problemCaseList) {
                if (problemCase.getScore() != null) {
                    sumScore += problemCase.getScore();
                }
            }
        }
        return sumScore;
    }
}
